package com.xcity.game.chat;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.xcity.Server;
import com.xcity.config.ConfigEx;
import com.xcity.db.entity.Guild;
import com.xcity.db.entity.GuildMember;
import com.xcity.db.entity.Player;
import com.xcity.game.common.MessageConst;
import com.xcity.game.guild.GuildService;
import com.xcity.game.player.PlayerService;
import com.xcity.game.player.cache.PlayerCacheService;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.push.ChatPush;
import com.xcity.pomelo.push.msg.DotTipPush;
import com.xcity.pomelo.request.ChatSendRequest;
import com.xcity.pomelo.request.GetChatRecordsRequest;
import com.xcity.pomelo.response.ChatRecordsResponse;
import com.xcity.pomelo.response.HintResponse;
import com.xcity.pomelo.response.Response;
import com.xcity.util.BackDoorHandler;
import com.xcity.util.SessionUtils;

import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import naga.x.App;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.Service;
import naga.x.service.ServiceException;
import naga.x.util.IOUtils;
import naga.x.util.IOUtils.LineReaderListener;
import naga.x.util.KeywordsManager;
import naga.x.util.NumberUtils;


@OPHandler
public class ChatService implements Service, Runnable {

	private static final int MAX_SIZE = 40;
	private static final int WORDS_MAX_NUM = 100; // 一条聊天最大字数
	private static final int CHAT_CD = 3000;
	// 公聊记录/系统公告(最多20条)
	private Queue<ChatMessage> pubQueue = new ConcurrentLinkedQueue<ChatMessage>();
	// 私聊(key:pid1-pid2)
	private Map<String, Queue<ChatMessage>> priQueue = new ConcurrentHashMap<String, Queue<ChatMessage>>();
	// 系统公告
	private Queue<ChatMessage> systemQueue = new ConcurrentLinkedQueue<ChatMessage>();
	
	private Map<Long, Queue<ChatMessage>> guildQueue = new ConcurrentHashMap<Long, Queue<ChatMessage>>();
	
	private KeywordsManager badWordsFilter;
	
	// 等待发送的聊天队列
	private BlockingQueue<ChatMessage> messages = new LinkedBlockingQueue<ChatMessage>();
	// 禁言列表
	private TLongObjectMap<Forbidden> forbiddens = new TLongObjectHashMap<Forbidden>();
	
	protected static final Logger LOG = LoggerFactory.getLogger(ChatService.class);
	
	@Override
	public void startup() throws ServiceException {
		badWordsFilter = ((ConfigEx) App.getApp().getConfig()).getBadwordsFilter();
		loadChatQueue();
		loadForbiddens();
		new Thread(this).start();
	}

	@Override
	public void shutdown() throws ServiceException {
		try {
			saveChatQueue();
			saveForbiddens();
		} catch (Exception e) {
			LOG.error("[SAVE CHAT ERROR]", e);
		}
	}
	
	private void loadChatQueue() {
		ConfigEx config = App.getApp().getConfig();
		File file = new File(config.localDbDir, "chat.txt");
		if (!file.exists()) {
			return;
		}
		IOUtils.read(file, IOUtils.UTF8, new LineReaderListener() {
			@Override
			public boolean readLine(String line) {
				ChatMessage msg = JSON.parseObject(line, ChatMessage.class);
				addChat(msg, true);
				return true;
			}
		});
	}
	
	private void saveChatQueue() {
		ConfigEx config = App.getApp().getConfig();
		File file = new File(config.localDbDir, "chat.txt");
		ChatMessage msg;
		StringBuilder buf = new StringBuilder(1024);
		while ((msg = systemQueue.poll()) != null) {
			buf.append(JSON.toJSONString(msg)).append('\n');
		}
		while ((msg = pubQueue.poll()) != null) {
			buf.append(JSON.toJSONString(msg)).append('\n');
		}
		Collection<Queue<ChatMessage>> priChats = priQueue.values();
		for (Queue<ChatMessage> queue : priChats) {
			while ((msg = queue.poll()) != null) {
				buf.append(JSON.toJSONString(msg)).append('\n');
			}
		}
		
		Collection<Queue<ChatMessage>> guildChats = guildQueue.values();
		for(Queue<ChatMessage> queue : guildChats){
			while((msg = queue.poll()) != null){
				buf.append(JSON.toJSONString(msg)).append('\n');
			}
		}
		if (buf.length() > 0) {
			buf.setLength(buf.length() - 1); // delete the last '\n'
		}
		IOUtils.write(file, buf.toString(), IOUtils.UTF8);
	}
	
	private void loadForbiddens() {
		ConfigEx config = App.getApp().getConfig();
		File file = new File(config.localDbDir, "chat_forbid.db");
		if (!file.exists()) {
			return;
		}
		List<Forbidden> list = JSON.parseArray(IOUtils.read(file, IOUtils.UTF8), Forbidden.class);
		for (Forbidden forbid : list) {
			if (forbid.endTime > TimeUpdater.getInstance().now()) {
				forbiddens.put(forbid.id, forbid);
			}
		}
	}
	
	private void saveForbiddens() {
		ConfigEx config = App.getApp().getConfig();
		File file = new File(config.localDbDir, "chat_forbid.db");
		String json = JSON.toJSONString(forbiddens.values(new Forbidden[forbiddens.size()]));
		IOUtils.write(file, json, IOUtils.UTF8);
	}

	@Override
	public String getId() {
		return getClass().getName();
	}
	
	private void addPrivateChat(ChatMessage chat) {
		String key = chat.getSrcId() + "-" + chat.getDestId();
		Queue<ChatMessage> queue = priQueue.get(key);
		if (queue == null) {
			key = chat.getDestId() + "-" + chat.getSrcId();
			queue = priQueue.get(key);
		}
		if (queue == null) {
			queue = new ConcurrentLinkedQueue<ChatMessage>();
			priQueue.put(key, queue);
		}
		queue.offer(chat);
		if (queue.size() > MAX_SIZE) {
			queue.poll();
		}
	}
	
	private void addGuildChat(ChatMessage chat){
		long guildId = chat.getDestId();
		Queue<ChatMessage> queue = guildQueue.get(guildId);
		if(queue == null){
			queue = new ConcurrentLinkedQueue<ChatMessage>();
			guildQueue.put(guildId, queue);
		}
		queue.offer(chat);
		if(queue.size() > MAX_SIZE){
			queue.poll();
		}
	}
	
	private Queue<ChatMessage> getGuildChatByGuildId(long gid){
		return guildQueue.get(gid);
	}
	
	private Queue<ChatMessage> getPrivateChat(long srcId, long destId) {
		String key = srcId + "-" + destId;
		Queue<ChatMessage> queue = priQueue.get(key);
		if (queue == null) {
			key = destId + "-" + srcId;
			queue = priQueue.get(key);
		}
		return queue;
	}
	
	private void addPublicChat(ChatMessage chat) {
		pubQueue.offer(chat);
		if (pubQueue.size() > MAX_SIZE) {
			pubQueue.poll();
		}
	}
	
	private void addSystemChat(ChatMessage chat) {
		systemQueue.offer(chat);
		if (systemQueue.size() > MAX_SIZE) {
			systemQueue.poll();
		}
	}
	
	private void addChat(ChatMessage chat) {
		addChat(chat, false);
	}
	
	private void addChat(ChatMessage chat, boolean reload) {
		switch (chat.channel) {
			case SYS:
				addSystemChat(chat);
				break;
			case PUB:
				addPublicChat(chat);
				break;
			case PRI:
				addPrivateChat(chat);
				break;
			case GLD:
				addGuildChat(chat);
				break;
		}
		if (!reload) {
			messages.offer(chat);
		}
	}
	
	@PomeloOP(PomeloRoute.SEND_CHAT)
	protected void sendChat(Session session, ChatSendRequest req) {
		Player src = session.getClient();
		if (wasForbidden(src)) {
			session.send(new HintResponse(req.getSerial(), MessageConst.CHAT_FORBIDDEN));
			return;
		}
		ChatChannel chnl = ChatChannel.valueOf(req.getScope());
		if (chnl == ChatChannel.PUB) {
			Long lastTime = session.getAttribute(SessionUtils.LAST_PUB_CHAT_TIME);
			if (lastTime != null && TimeUpdater.getInstance().now() - lastTime < CHAT_CD) {
				// 限制每3秒发一次公聊
				session.send(new HintResponse(req.getSerial(), MessageConst.CHAT_IN_CD));
				return;
			}
			session.setAttribute(SessionUtils.LAST_PUB_CHAT_TIME, TimeUpdater.getInstance().now());
		}
		String content;
		if (req.getContent() == null || (content = req.getContent().trim()).isEmpty()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.CHAT_LENGTH_ZERO));
			return;
		}
		if (content.length() > WORDS_MAX_NUM) {
			session.send(new HintResponse(req.getSerial(), MessageConst.CHAT_CONTENT_TOO_LONG));
			return;
		}
		String oldContent = content;
		if (badWordsFilter != null) {
			content = badWordsFilter.filterBadWords(content);
		}
		
		// 私聊
		if (ChatChannel.PRI == chnl) {
			Player dest = App.getApp().getServiceManager().get(PlayerCacheService.class).find(req.getToID());
			if (dest == null) {
				session.send(new HintResponse(req.getSerial(), MessageConst.PLAYER_NOT_EXIST));
				return;
			}
			addChat(new ChatMessage(src, dest, content));
			session.send(new Response(req.getSerial()));
//			// push
//			ChatPush push = new ChatPush(chnl.name(), src.getId(), src.getName(), src.getIcon(), content);
//			src.send(push);
//			dest.send(push);
//			dest.send(DotTipPush.NEW_CHAT);
			return;
		}
		// 公聊
		if (ChatChannel.PUB == chnl) {
			if (BackDoorHandler.handle(session.getClient(), req.getSerial(), oldContent)) {
//				session.send(new Response(req.getSerial()));
				return;
			}
			addChat(new ChatMessage(src, content));
//			Collection<Player> players = ps.getOnlinePlayers();
//			ChatPush push = new ChatPush(chnl.name(), src.getId(), src.getName(), src.getIcon(), content);
//			for (Player p : players) {
//				p.send(push);
//				p.send(DotTipPush.NEW_CHAT);
//			}
			session.send(new Response(req.getSerial()));
			return;
		}
		
		if (ChatChannel.GLD == chnl){
			GuildService gs = App.getApp().getServiceManager().get(GuildService.class);
			GuildMember member = gs.getGuildMember(((Player)session.getClient()).getId());
			if(member != null){
				if(member.getGuildid() > 0){
					addChat(new ChatMessage(src, member.getGuildid(), content));
					session.send(new Response(req.getSerial()));
					return;
				}
			}
		}
		session.send(new HintResponse(req.getSerial(), MessageConst.CHAT_CHANNEL_NOT_EXIST));
	}
	
	@PomeloOP(PomeloRoute.CHAT_RECORDS)
	protected void getChatRecords(Session session, GetChatRecordsRequest req) {
		long pid = NumberUtils.parseLong(req.getTargetRoleId());
		int offset = req.getOffset() < 0 ? 0 : req.getOffset();
		int count = req.getCount() > MAX_SIZE ? MAX_SIZE : req.getCount();
		if (pid != 0) { // 私聊
			Player player = session.getClient();
			Queue<ChatMessage> queue = getPrivateChat(player.getId(), pid);
			session.send(new ChatRecordsResponse(req.getSerial(), ChatChannel.PRI, queue != null ? queue : Collections.emptyList(), offset, count));
		} else if (req.getChannel() == ChatChannel.SYS) { // 系统公告
			session.send(new ChatRecordsResponse(req.getSerial(), ChatChannel.SYS, systemQueue, offset, count));
		} else if (req.getChannel() == ChatChannel.GLD) { // 公会
			Player player = session.getClient();
			GuildService gs = App.getApp().getServiceManager().get(GuildService.class);
			if(gs != null){
				Guild guild = gs.findGuildByPlayerId(player.getId());
				if(guild != null){
					Queue<ChatMessage> queue = getGuildChatByGuildId(guild.getId());
					player.send(new ChatRecordsResponse(req.getSerial(), ChatChannel.PUB, queue != null ? queue : Collections.emptyList(), offset, count));
					return;
				}
			}
			player.send(new HintResponse(req.getSerial(), MessageConst.GUILD_NOT_IN_GUILD));
		} else { // 公聊
			session.send(new ChatRecordsResponse(req.getSerial(), ChatChannel.PUB, pubQueue, offset, count));
		}
	}
	
	public void sendSystemChat(String content) {
		ChatMessage chat = new ChatMessage(content);
		addChat(chat);
//		Collection<Player> players = App.getApp().getServiceManager().get(PlayerService.class).getOnlinePlayers();
//		ChatPush push = new ChatPush(ChatChannel.SYS.name(), chat.getSrcId(), chat.getSrcName(), chat.getSrcIcon(), content);
//		for (Player p : players) {
//			p.send(push);
//		}
	}

	@Override
	public void run() {
		while (true) {
			ChatMessage chat;
			try {
				chat = messages.take();
				ChatPush push = new ChatPush(chat.getChannel().name(), chat.getSrcId(), chat.getSrcName(), chat.getSrcIcon(), chat.getContent());
				if (chat.getChannel() == ChatChannel.PRI) {
					PlayerService ps = App.getApp().getServiceManager().get(PlayerService.class);
					Player src = ps.getOnlinePlayer(chat.getSrcId());
					if (src != null) {
						src.send(push);
					}
					Player dest = ps.getOnlinePlayer(chat.getDestId());
					if (dest != null) {
						dest.send(push);
						dest.send(DotTipPush.NEW_CHAT);
					}
				} else if(chat.getChannel() == ChatChannel.GLD){
					GuildService gs = App.getApp().getServiceManager().get(GuildService.class);
					gs.sendPushForGuildChannel(chat, push);
				} else {
					// broadcast
					Server.broadcast(push, DotTipPush.NEW_CHAT);
				}
				LOG.info("[CHAT]CHANNEL[{}]SRC[{}]DEST[{}]MSG[{}]", chat.channel, chat.getSrcId(), chat.getDestId(), chat.getContent());
			} catch (Exception e) {
				LOG.error(e.getMessage(), e);
			}
		}
	}
	
	private boolean wasForbidden(Player player) {
		Forbidden fb = forbiddens.get(player.getId());
		if (fb == null) {
			return false;
		}
		if (fb.endTime < TimeUpdater.getInstance().now()) {
			// 解禁
			forbiddens.remove(player.getId());
			return false;
		}
		return true;
	}
	
	public Forbidden[] getForbiddens() {
		return forbiddens.values(new Forbidden[forbiddens.size()]);
	}
	
	public void addForbidden(long id, long endTime) {
		Forbidden fb = new Forbidden();
		fb.id = id;
		fb.endTime = endTime;
		forbiddens.put(id, fb);
	}
	
	public Forbidden removeForbidden(long id) {
		return forbiddens.remove(id);
	}
	
	public static class Forbidden {
		public long id; // 被禁言玩家id
		public long endTime; // 禁言截止时间
	}

}
